#ifndef MODEL_REQUIREMENT_HPP
#define MODEL_REQUIREMENT_HPP

#include "Model/CaseEntity.hpp"
#include "Model/Comment.hpp"
#include <limits>
#include <cstddef>
#include <list>

namespace Model
{

class Package;


/// @brief It describes a requirement.
/// 
/// A requirement is characterized by a name, which should be unique between the siblings, a statement, which is the 
///	actual text of the requirement, a full identifier, calculated as concatenation of parent identifier and 
///	requirement's position inside the parent's collection of children. 
/// <br>
/// The requirement structure is hierarchical. A requirement can contain child requirements.
/// <br>
/// It holds a reference (pointer) to a requirement manager, used for signaling various events, such as name changing, 
///	movement, etc.
class MODEL_API Requirement: public CaseEntity
{
public:
	typedef std::list< Requirement*> ChildCollection;
	typedef ChildCollection::iterator ChildIterator;
	typedef ChildCollection::const_iterator ChildConstIterator;

	typedef std::list< Comment> CommentCollection;
	typedef CommentCollection::iterator CommentIterator;
	typedef CommentCollection::const_iterator CommentConstIterator;

public:
	/// @brief It initializes a requirement with no parent.
	/// @param name the name of the requirement.
	/// @param statement the statement of the requirement.
	/// @note The position of the requirement is set to 0.
	Requirement( const NameString& name, const RichText& statement = RichText());

	/// @brief It deletes all the child requirements and comments.
	virtual ~Requirement();

	/// @brief It gets the statement of this requirement.
	const RichText& statement() const { return statement_; }

	/// @brief It sets the statement of the requirement
	void setStatement( const RichText& statement);

	/// @brief It gets the position of the statement.
	std::size_t position() const { return position_; }


	/// @brief It sets the name of the requirement.
	/// @param name the new name of the requirement.
	/// @throw DuplicateNameError if the name is already in use by another sibling.
	///
	/// If the specified name is the same as the requirement's existing name, nothing happens.
	void setName( const NameString& newName);

	/// @brief It gets the name of the requirement.
	const NameString& name() const { return name_;}


	/// @brief It calculates the unique identifier for this requirement.
	/// 
	/// The unique identifier is calculated by getting the parent's unique identifier and appending "." and the position 
	///	number of this requirement inside the siblings collection.
	String id() const;

	/// @brief It gets the iterator to the beginning of child requirements sequence.
	/// @{
	ChildIterator childrenBegin() { return childRequirements_.begin(); }
	ChildConstIterator childrenBegin() const { return childRequirements_.begin(); }
	/// @}

	/// @brief It gets the iterator to the end of the child requirements sequence.
	/// @{
	ChildIterator childrenEnd() { return childRequirements_.end(); }
	ChildConstIterator childrenEnd() const { return childRequirements_.end(); }
	/// @}

	/// @brief It gets the number of child requirements.
	std::size_t numberOfChildren() const { return childRequirements_.size(); }

	/// @brief It checks whether the specified name is in use by one of the child requirements.
	bool hasChildWithName( const std::string& name) const;


	/// @brief It creates a new requirement and inserts it at the specified position.
	/// @param position the position the requirement should be moved to (1 based). If the position is greater than the 
	///		number of children of this requirement, it is adjusted to the end position of the children collection.
	/// @param name the name of the requirement to be created.
	/// @param position the position to insert the new requirement at. By default, it indicates the end of child 
	///		requirements collection.
	/// @param statement the statement of the requirement.
	/// @throw DuplicateNameError if the name of the new requirement is already used by one of the siblings.
	///
	/// It increments the positions of the children that are stored after the insertion position. If the insertion 
	///	position is larger than the number of child requirements, the new requirement is inserted at the end. If the 
	///	insertion position is 0, then it is inserted at the beginning.
	Requirement* addChildRequirement( 
		const NameString& name, 
		std::size_t position = std::numeric_limits< std::size_t>::max(), 
		const RichText& statement = RichText());

	/// @brief It inserts the specified requirement at the specified position.
	/// @param pRequirement the requirement to be inserted.
	/// @param position the position between the child requirements the specified one should be inserted at.
	/// @throw InvalidHierarchyOperationError if the requirement to be moved is the parent of this requirement.
	///
	/// It affects the children of the former parent of the specified requirement, if any.
	Requirement* addChildRequirement( 
		Requirement* pRequirement, std::size_t position = std::numeric_limits< std::size_t>::max());


	/// @brief It removes the requirement from the children collection.
	/// 
	/// @warning The requirement and its children are not deleted! You have to delete them with your 
	///		own hand!
	Requirement* removeChildRequirement( Requirement* pRequirement);

	/// @brief It searches a child requirement by name. It doesn't perform recursive search.
	/// @param name The name of the requirement to be found.
	/// @return the requirement with the specified name or NULL if no requirement was found.
	Requirement* findByName( const NameString& name) const;

	/// @brief It gets the parent of this requirement.
	/// @return parent requirement or NULL if this is a root requirement.
	/// @{
	Requirement& parent() { return *pParentRequirement_; }
	const Requirement& parent() const { return *pParentRequirement_; }
	/// @}

	/// @brief It checks whether this requirement is a root requirement.
	/// @return true if this requirement has a parent or false otherwise.
	bool hasParent() const { return NULL != pParentRequirement_; }

	/// @brief It checks whether the specified requirement is part of the hierarchy branch managed by this requirement.
	/// @param requirement the requirement to be searched.
	bool isAncestorOf( const Requirement& requirement) const;


	/// @brief It moves the requirement to the specified package.
	/// @pre This requirement is a root requirement.
	///
	/// It also alters the children to belong to the specified package.
	void moveToPackage( Model::Package* pNewPackage);

	/// @brief It gets the package this requirement belongs to.
	/// @{
	const Model::Package* package() const { return pParentPackage_; }
	Model::Package* package() { return pParentPackage_; }
	/// @}

	/// @brief It gets the requirement at the specified position.
	/// @pre the specified position is valid (less than the number of child requirements).
	Requirement* findByPosition( std::size_t position) const;

	/// @brief It gets the iterator to the beginning of the comment collection.
	/// @{
	CommentIterator commentsBegin() { return comments_.begin(); }
	CommentConstIterator commentsBegin() const { return comments_.begin(); }
	/// @}

	/// @brief It gets the iterator to the end of the comment collection.
	/// @{
	CommentIterator commentsEnd() { return comments_.end(); }
	CommentConstIterator commentsEnd() const { return comments_.end(); }
	/// @}

	/// @brief It inserts a new comment at the specified position.
	/// @param comment the comment to be inserted.
	/// @param position the position to insert the comment at. If the position is higher than the number of already 
	///		existing comments, the specified comment is appended. If no position is specified, the comment is appended.
	void insertComment( const Comment& comment, std::size_t position = std::numeric_limits< std::size_t>::max());

	/// @brief It removes the comment at the specified position.
	/// @param position the position to remove the comment from.
	/// @pre the specified position is valid (less than the number of comments).
	void removeComment( std::size_t position);

	/// @brief It changes the comment at the specified position.
	/// @param position the position to change the comment at.
	/// @param comment the comment to replace the old one.
	/// @pre the specified position is a valid one (less than the number of requirement comments).
	void changeComment( const Comment& comment, std::size_t position);

	/// @brief It gets the comment at the specified position.
	/// @param position the position to change the comment at.
	/// @{
	Comment& commentAt( std::size_t position);
	const Comment& commentAt( std::size_t position) const;
	/// @}

protected:
	/// @brief It increments the position of this requirement. 
	/// @pre The current position is not indicating the end of the child collection.
	///
	/// No check is made to find out whether this position corresponds to the real position in parent's child list.
	void incrementPosition();

	/// @brief It decrements the position of this requirement. 
	/// @pre The current position is not indicating the beginning of the child collection.
	///
	/// No check is made to find out whether this position corresponds to the real position in parent's child list.
	void decrementPosition();

	/// @brief It sets the position of this requirement.
	///
	/// No check is made to find out whether this position corresponds to the real position in parent's child list.
	void setPosition( std::size_t position);
	
private:
	RichText statement_;
	std::size_t position_;
	ChildCollection childRequirements_;
	Requirement* pParentRequirement_;
	CommentCollection comments_;
	NameString name_;
	Model::Package* pParentPackage_;

private:
	/// @brief It inserts the specified child requirement in the collection at the specified position and updates the 
	///		positions of the rest of requirements after the insertion point.
	/// @param pChild the requirement to be inserted in child requirements collection.
	/// @param position the iterator indicating the insertion point.
	void insertChild( Requirement* pChild, ChildIterator position);

	/// @pre The specified position is in the range of the collection.
	ChildIterator findChildInsertionPosition( std::size_t position);

	/// Removes the child requirement, with no triggering of the events and stuff. For internal use 
	///		only.
	/// @pre The specified child requirement is really a child of this requirement.
	void removeChildRequirementWithNoChecks( Requirement* pRequirement);

	/// Adds the child requirement from another parent to this requirement.
	/// @param position the position the requirement should be moved to (1 based). If the position 
	///		is greater than the number of children of this requirement, it is adjusted to the end 
	///		position of the children collection.
	/// @pre The specified requirement is not a child requirement of this requirement.
	/// @pre The specified requirement should have the same manager as this requirement or it should 
	///		be an orphan requirement.
	void addChildRequirementFromAnotherParent( Requirement* pRequirement, std::size_t position);

	/// @param position the position the requirement should be moved to (1 based). If the position 
	///		is greater than the number of children of this requirement, it is adjusted to the end 
	///		position of the children collection.
	/// Moves the specified requirement inside this parent to the specified position.
	/// @pre The specified requirement is a child of this requirement.
	void moveChild( Requirement* pRequirement, std::size_t position);

	/// @return the iterator at the specified position.
	/// @pre The specified position is in the range of comments_ collection. The position is 1-based.
	/// @{
	CommentIterator findCommentIterator( std::size_t position);
	CommentConstIterator findCommentIterator( std::size_t position) const;
	/// @}


	/// @brief It sets the package for this requirement and for each of its child requirements, recursively.
	void setPackage( Model::Package* pPackage);
};

} // namespace Model


#endif // MODEL_REQUIREMENT_HPP
